在javascript中,我们知道可以使用对象字面量或者构造函数创建对象,但是如何优雅地创建一个对象你却不一定了解。
前人在踩过无数坑又填过无数坑之后,给我们总结了不同场景下的几种对象创建模式:
- 命名空间模
- 模块模式
- 沙箱模式
- 链模式
命名模式
有这样一种场景,假如你正在写一个插件,这个插件内部会用到很多的全局变量,这时候你要怎么保证你的变量不会与其他插件的变量产生命名冲突呢?我们知道,在javascript中并没有内置命名空间,后面的变量会覆盖掉前面的变量,你要如何避免这种问题的发生?
可能你已经想到,只要足够独特的命名就可以了吧,比如lynneShowVar,这样确实是可以的,但是如果变量很多,难道要为每个都起一个很独特的命名?是不是很累?而且维护也是很麻烦的。
命名模式提出,为应用程序创建一个全局对象,比如MYAPP,然后将所有的变量和函数挂到这个全局对象(MYAPP)的属性上。
//创建全局对象
var MYAPP = {};
//构造函数
MYAPP.parent = function () {};
MYAPP.child = function () {};
//一个变量
MYAPP.some_var = 1;
//一个对象容器
MYAPP.modules = {}
//嵌套对象
MYAPP.modules.module1 = {};
MYAPP.modules.module2 = {};
命名约定:通常以全部大写的方式来命名这个全局对象。
优点:
- 避免代码中的命名冲突
- 避免代码与第三方的命名冲突
缺点
- 需要输入更多字符
- 任何部分的代码都可以修改全局实例
- 嵌套的额数字意味着更长的属性查询解析。
那么,有什么办法可以避免这些缺陷么?声明代码依赖关系应该是一个不错的主意了。
声明依赖关系
var myFunction = function () {
//依赖
var event = MYAPP.util.event,
dom = MYAPP.util.dom;
//...
}
优点:
- 可读性强
- 解析局部变量的速度总是比解析全局变量的速度快
- 减小代码量
通用命名空间
随着程序的复杂度的增加,如何保证全局对象上新添属性不会覆盖原有的属性呢。这就需要每次在添加新属性之前先检查它是否已经存在了。
var MYAPP = MYAPP || {};
MYAPP.name = function (ns_string) {
var parts = ns_string.split('.'),
parent = MYAPP,
i;
//剥离最前面的冗余全局变量
if (parts[0] === 'MYAPP') {
parts = parts.slice(1);
}
for(i = 0; i < parts.length; i++) {
//如果不存在就创建一个属性
if (typeof parent[parts[i]] === 'undefined') {
parent[parts[i]] = {};
}
parent = parent[parts[i]];
}
return parent;
}
命名模式虽然好用,但还是有个问题,那就是任何部分的代码都可以修改全局实例,以及里面的属性,怎么避免?我们知道es5并没有类的概念,如果要实现私有成员,可以使用闭包来 模拟这种特性。
模块模式
其实模块模式是多种模式的组合
- 命名空间
- 即时函数
- 私有和特权函数
- 声明依赖
MYAPP.ntilities.array = (function () {
//依赖
var uobj = MYAPP.ntilities.object,
ulang = MYAPP.ntilities.lang;
//私有属性
var array_string = '[object array]',
ops = Object.prototype.toString;
//私有方法
//...
//其它
//共有API
return {
inArray: function (needle, haystack) { ... },
isArray: function (str) { ... }
//更多...
}
})()
将全局变量导入到模块中
可以将参数传递到包装了模块的即时函数中,有助于加速即时函数中全局符号的解析。
MYAPP.ntilities.array = (function (app, global) {
}(MYAPP, this);
沙箱模式
假设需要在同一个页面运行同一个库的两个版本,很遗憾,前面两种创建模式都是不支持的。这时候就需要模块化。
在命名模式中,有一个全局对象 ,在沙箱模式中,有一个全局构造函数,我们这里命名为Sandbox()。这个构造函数可以接收一个或多个参数,这些参数指定对象实例需要的模块名,以及一个回调函数。如:
//模块名可以使用数组的形式传参
Sandbox(['ajax', 'dom'], function () { //这里是回调函数 });
//模块名也可以使用单个参数的形式传参,参数之间使用,号隔开
Sandbox('ajax', 'dom', function () { //这里是回调函数 });
//不指定模块名称或指定'*',表示需要依赖所有模块
Sandbox(function () { //这里是回调函数 });
Sandbox('*', function () { //这里是回调函数 });
在实现实际的构造函数之前,先为Sandbox添加一个名为modules的静态属性,这需要理解的是Sandbox同时也是一个对象。
Sandbox.modules = {}
把需要的模块添加到Sandbox.modules对象中。假设我们一共需要dom、ajax、event模块
Sandbox.modules.dom = function(box) {
box.getElement = function () {};
};
Sandbox.modules.ajax = function(box) {
box.getResponse = function () {};
};
Sandbox.modules.event = function(box) {
box.attachEvent = function () {};
};
接下来实现构造函数
function Sandbox = function () {
//将参数转换成一个数组
var args = Array.prototype.slice.call(arguments),
//最后一个参数是回调函数
callback = args.pop(),
//args[0]如果是String类型,说明模块作为单独参数传递,否则模块作为数组形式传递
modules = (args[0] && typeof args[0] === 'String') ? args : arg[0],
i;
//确保该函数作为构造函数调用
if(!(this instanceof Sandbox)) {
return new Sandbox (modules, callback);
}
//需要向this添加的属性,也可能没有,这里看实际项目需求
this.a = 1;
this.b = 2;
//现在向该核心'this'对象添加模块
//不指定模块名称或指定'*',都表示制定所有模块
if (!modules || modules === '*') {
modules = [];
for (i in Sandbox.modules) {
modules.push[i];
}
}
//初始化所需的模块
for(i = 0; i < modules.lenght; i++) {
Sandbox.modules[modules[i]](this)
}
//依赖模块已全部初始化,可以执行回调函数
callback();
}
//获取你还需要添加一些原型属性,看需求
Sanndbox.prototype = {
name: 'MY Application',
version: '1.0',
getName: function () {
return this.name;
}
};
优点
- 实现模块化,解决 同一个页面不能 使用同一个库的不同版本的问题
- 优化了以点分割的名字的解析时间,如:MYAPP.untilities.array
链式模式
链式模式可以使您能够一个接一个地调用对象的方法,而无需将前一个操作返回的值付给变量,且无需分割成多行。如:
myobj.method1('hello').method2().method3('str');
这个很好理解,我们直接来看一看代码
var obj = {
al: 1,
add: function (v) {
this.val += v;
return this;
},
set: function (v) {
this.val = v;
return this;
},
shout: function (v) {
console.log(this.val);
}
};
//链方法调用
obj.add(1).set(2).shout();
可以看到,obj的每个方法都返回this对象,这就是实现链式的原理。
优点
- 节省输入的字符
- 有助于分割函数,提高代码的维护性
缺点
- 难以调试,一旦出现问题,难以定位到具体步骤
每种设计模式都各有优缺点,具体使用哪种模式还得看项目需求,大家一起学习吧。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。